﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;
using System.Diagnostics;
using System.Text.RegularExpressions;

namespace CppIDE.Core
{

    /// <summary>
    /// Egy C++ (vagy C) fájlt reprezentáló osztály, a SourceFile-ból származtatva.
    /// </summary>
    public class CppFile : SourceFile
    {

        /// <summary>
        /// Üres konstruktor
        /// </summary>
        public CppFile()
            : base()
        {
        }

        /// <summary>
        /// Konstruktor, létrehoz egy példányt a megadott elérési úttal.
        /// </summary>
        /// <param name="fullPath">A teljes elérési út</param>
        public CppFile(String fullPath)
            : base(fullPath)
        {
        }

        /// <summary>
        /// Lefordítja a forrásfájlt. Ha nincs rá szükség, mert az eredmény újabb, mint
        /// a fájl és használt header fájlok, akkor nem fordít. (lásd: inkrementális fordító)
        /// </summary>
        /// <param name="compilerParams">a fordító paraméterei</param>
        /// <param name="success">igaz, ha sikerült a fordítás (akkor is, ha nem volt rá szükség)</param>
        /// <returns>Visszatér CompileError-ok listájával (lehet, hogy üres)</returns>
        public List<CompileError> compile(String compilerParams, out bool success)
        {
            List<CompileError> errors = new List<CompileError>();
            if (!isUpToDate())
            {
                Application application = Application.getInstance();
                String gccPath = application.Config.GccPath;
                String gppBin = gccPath + "bin\\g++.exe";
                ProcessStartInfo startInfo = new ProcessStartInfo(gppBin);
                startInfo.Arguments = compilerParams;
                startInfo.Arguments += " -c \"" + FullPath + "\"";
                startInfo.Arguments += " -o \"" + output.FullPath + "\"";
                startInfo.CreateNoWindow = true;
                startInfo.UseShellExecute = false;
                startInfo.RedirectStandardError = true;
                Process process = Process.Start(startInfo);
                String stdError = process.StandardError.ReadToEnd();

                String pattern = @"^(.*?):((\d+):)?(\d+:)?\s+(.+)$";
                Regex regex = new Regex(pattern);
                String[] lines = stdError.Split('\n');
                foreach (String line in lines)
                {
                    String message = line.Trim();
                    if (message.Length == 0) continue;
                    MatchCollection matches = regex.Matches(message);
                    if (matches.Count == 0)
                    {
                        errors.Add(new CompileError(message));
                    }
                    else
                    {
                        Match match = matches[0];
                        String fileName = match.Groups[1].Value;
                        SourceFile container;
                        if (fileName.Equals(FullPath))
                        {
                            container = this;
                        }
                        else
                        {
                            container = SourceFile.factory(fileName);
                        }
                        message = match.Groups[5].Value.TrimEnd();
                        try
                        {
                            int lineNumber = Int32.Parse(match.Groups[3].Value);
                            errors.Add(new CompileError(message, lineNumber, container));
                        }
                        catch (Exception)
                        {
                            errors.Add(new CompileError(message, container));
                        }
                    }
                }
                success = process.ExitCode == 0;
            }
            else
            {
                success = true;
            }
            return errors;
        }

        /// <summary>
        /// Visszaadja az include-olt HeaderFile-ok listáját.
        /// </summary>
        /// <returns>Az include-olt HeaderFile-ok (lehet, hogy üres)</returns>
        private List<HeaderFile> getIncludes()
        {
            List<HeaderFile> includes = new List<HeaderFile>();

            string source = Content;

            // remove c style (multi-line) comments
            source = Regex.Replace(source, @"\/\*.*?\*\/", "");
            // remove c++ style (one-line) comments
            source = Regex.Replace(source, @"\/\/[^\n]*", "");

            String pattern = @"#include\s+""([^""]+)""";
            MatchCollection matches = Regex.Matches(source, pattern, RegexOptions.IgnoreCase);
            foreach (Match match in matches)
            {
                string include = match.Groups[1].Value;
                include = include.Replace("/", @"\");
                SourceFile sourceFile = SourceFile.factory(ParentPath + "\\" + include);
                if (sourceFile is HeaderFile)
                {
                    includes.Add(sourceFile as HeaderFile);
                }
            }
            return includes;
        }

        /// <summary>
        /// Ha igaz, akkor nincs szükség fordításra. (lásd: inkrementális fordító)
        /// </summary>
        /// <returns>friss-e a kimenet?</returns>
        private bool isUpToDate()
        {
            if (!Output.Exist)
            {
                return false;
            }
            else
            {
                DateTime outputDate = Output.LastModification;
                int compareResult = outputDate.CompareTo(LastModification);
                if (compareResult < 0)
                {
                    return false;
                }
                List<HeaderFile> includes = getIncludes();
                foreach (HeaderFile include in includes)
                {
                    if (!include.Exist)
                    {
                        return false;
                    }
                    compareResult = outputDate.CompareTo(include.LastModification);
                    if (compareResult < 0)
                    {
                        return false;
                    }
                }
                return true;
            }
        }

        /// <summary>
        /// A kimenet (objektfájl)
        /// </summary>
        private ObjectFile output = null;

        /// <summary>
        /// A kimenet tulajdonság (objektfájl). Lekérdezéséhez be kell, hogy legyen
        /// állítva a File-tól örökölt FullPath (azaz a teljes elérési út).
        /// </summary>
        /// <value>A kimenet (objektfájl)</value>
        [XmlIgnore]
        public ObjectFile Output
        {
            get
            {
                if (output == null)
                {
                    if (FullPath == null)
                    {
                        throw new Exception("Invalid state: FullPath have to be set before getting the output file!");
                    }
                    int dotPosition = FullPath.LastIndexOf('.');
                    String prefix = FullPath.Substring(0, dotPosition);
                    output = new ObjectFile(prefix + ".o");
                }
                return output;
            }
        }

    }

}
